package com.fredhat.tvshowrenamer;

import java.io.File;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.regex.*;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import com.fredhat.tvshowrenamer.matcher.*;

public class TVShowRenamer {
	public static final long FILE_IDLE_TIME = 300000; // 5 minutes

	private Logger logger;
	private Configuration config;
	private EpisodeMatcher[] episodeMatchers;
	
	public TVShowRenamer() {
		PropertyConfigurator.configure(System.getProperty("app.dir")+File.separator+
				"conf"+File.separator+"log4j.properties");
		logger = Logger.getLogger(this.getClass());
		logger.info("Starting.");
		
		config = Configuration.getConfiguration();		
		episodeMatchers = new EpisodeMatcher[]{
			new SeasonMatcher(config),
			new DateMatcher(config)
		};

		if (config.useRecursiveDirectories())
			logger.info("We are processing directories recursively.");
		else
			logger.info("We are not processing directories recursively.");
		while(true) {
			logger.info("Processing files from "+config.getIncomingDir());
			process(new File(config.getIncomingDir()));
			
			if (config.getSleepTime() == 0) {
				logger.info("Finished.");
				System.exit(0);
			}
			
			logger.info("Sleeping.");
			try {
				Thread.sleep(config.getSleepTime());
			} catch (InterruptedException e) {}
		}
	}

	public void process(File incomingDir) {
		logger.debug("Examining files from "+incomingDir.getName()+".");
		
		File[] files = incomingDir.listFiles();
		for(int i=0; i<files.length; i++) {
			logger.debug("Processing file "+files[i].getName());
			if (files[i].isDirectory()) {
				if (config.useRecursiveDirectories()) {
					logger.debug("This is a directory.  Recursively processing.");
					process(files[i]);
				} else {
					logger.debug("This is a directory.  Not processing recursively.  Skipping.");
				}
				
				continue;
			}
			
			// Make sure this file has not recently been written to
			if (System.currentTimeMillis() - files[i].lastModified() < FILE_IDLE_TIME) {
				logger.debug("File "+files[i].getName()+
						" has been modified recently.  Skipping.");
				continue;
			}
			
			// Check TV Show information to identify file
			TVShow tvShow = getTvShowFromFile(files[i]);
			if (tvShow == null) {
				logger.debug("File "+files[i].getName()+" is not a recognized TV show.");
				continue;
			}
			
			TVShowEpisode episode = getTvShowEpisodeFromFile(tvShow, files[i]);
			if (episode != null) {
				changeFile(files[i], tvShow, episode);
			}
		}
	}
	
	// Returns an associated TVShow with file, or null if no match is found
	private TVShow getTvShowFromFile(File file) {
		if (file == null || file.isDirectory() || !file.exists())
			return null;

		logger.debug("Fetching TVShow from file "+file.getName());
		for(int i=0;i<config.getTVShows().length; i++) {
			TVShow tvShow = config.getTVShows()[i];
			for(int j=0; j<tvShow.getNameRegexes().length; j++) {
				logger.debug("Checking pattern "+tvShow.getNameRegexes()[j]);
				Pattern pattern = Pattern.compile(tvShow.getNameRegexes()[j], 
						Pattern.CASE_INSENSITIVE);
				Matcher matcher = pattern.matcher(file.getName());
				if (matcher.find()) {
					logger.debug("Match found: "+tvShow);
					return tvShow;
				}
			}
		}

		logger.debug("No TVShow found for "+file.getName());
		return null;
	}
	
	// Returns a parsed TVShowEpisode from the file, or null if no episode is found
	private TVShowEpisode getTvShowEpisodeFromFile(TVShow tvShow, File file) {
		if (file == null || file.isDirectory() || !file.exists())
			return null;
		
		logger.debug("Fetching TVShowEpisode from file "+file.getName());
		for(int i=0; i<episodeMatchers.length; i++) {
			EpisodeMatcher episodeMatcher = episodeMatchers[i];
			TVShowEpisode episode = episodeMatcher.matchingShowEpisode(tvShow, file);
			if (episode != null)
				return episode;
		}

		logger.debug("No TVShowEpisodes could be found for file "+file.getName());
		return null;
	}
	
	private void changeFile(File file, TVShow tvShow, TVShowEpisode episode) {
		logger.debug("Moving "+tvShow.getName()+" S"+episode.getSeason()+
				"E"+episode.getEpisode()+" to correct location");
		File dir = null;
		if (tvShow.getSeparateSeasons()) {
			dir = new File(config.getOutgoingDir()+File.separator+tvShow.getName()+
					File.separator+"Season "+episode.getSeason());
		} else {
			dir = new File(config.getOutgoingDir()+File.separator+tvShow.getName());
		}
		if (!dir.exists() && !dir.mkdirs()) {
			logger.error("Could not create directory "+dir.getName());
			return;
		}
		
		NumberFormat numFormat = new DecimalFormat("00");
		String newFilename = tvShow.getTemplate();
		newFilename = newFilename.replaceAll("%n", tvShow.getName());
		newFilename = newFilename.replaceAll("%s", numFormat.format(episode.getSeason()));
		newFilename = newFilename.replaceAll("%e", numFormat.format(episode.getEpisode()));
		newFilename = newFilename.replaceAll("%t", episode.getTitle());
		newFilename = newFilename+"."+tvShow.getExtension();
		// XXX - Add episode air date replacement here
		
		newFilename = convertForbiddenCharacters(newFilename);
		
		if (!config.isDryRun()) {
			if (!file.renameTo(new File(dir, newFilename))) {
				logger.error("Could not move file "+file.getName()+" to "+
						dir.getAbsolutePath()+File.separator+newFilename);
				return;
			}

			logger.info("Renamed "+file.getName()+" to "+dir.getAbsolutePath()+
					File.separator+newFilename);
		} else {
			logger.info("DRY RUN.  Would have renamed "+file.getName()+" to "+
					dir.getAbsolutePath()+File.separator+newFilename);
		}
		
	}
	
	private String convertForbiddenCharacters(String str) {
		logger.debug("String before forbidden char conversion: "+str);
		Configuration.ForbiddenCharacters fc[] = config.getForbiddenCharacters();
		for (int i=0; i<fc.length; i++) {
			logger.debug("Replacing "+fc[i].getRegex()+" with "+fc[i].getChangeTo());
			Pattern pattern = Pattern.compile(fc[i].getRegex());
			Matcher matcher = pattern.matcher(str);
			str = matcher.replaceAll(fc[i].getChangeTo());
		}
		
		logger.debug("String after forbidden char conversion: "+str);
		return str;
	}
}
